package com.gbas.comu.dao.impl;

import com.gbas.comu.dao.DAO;
import com.gbas.comu.dao.JPADAO;
import com.gbas.comu.util.EMF;
import com.gbas.comu.util.EntityManagerUtil;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.*;

import javax.persistence.EntityManager;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public abstract class JPADAOImpl<T, ID extends Serializable> implements DAO<T, ID>, JPADAO<T, ID> {
    protected final Class<T> entityClass;
//    private EntityManager entityManager;


    @SuppressWarnings({"unchecked"})
    public JPADAOImpl() {
        this.entityClass = (Class<T>) ((ParameterizedType) getClass()
                .getGenericSuperclass()).getActualTypeArguments()[0];
        //setEntityManager(EMF.get().createEntityManager());
    }

    public JPADAOImpl(final Class<T> entityClass) {
        super();
        this.entityClass = entityClass;
        //setEntityManager(EMF.get().createEntityManager());
    }

    @Override
    public int countAll() {
        return countByCriteria();
    }

    @Override
    public int countByExample(final T exampleInstance) {
        Session session = (Session) getEntityManager().getDelegate();
        Criteria crit = session.createCriteria(getEntityClass());
        crit.setProjection(Projections.rowCount());
        crit.add(Example.create(exampleInstance));

        return (Integer) crit.list().get(0);
    }

    @Override
    public List<T> findAll() {
        return findByCriteria();
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<T> findByExample(final T exampleInstance) {
        Session session = (Session) getEntityManager().getDelegate();
        Criteria crit = session.createCriteria(getEntityClass());
        final List<T> result = crit.list();
        return result;
    }

    @Override
    public T findById(final ID id) {
        final T result = getEntityManager().find(entityClass, id);
        return result;
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<T> findByNamedQuery(final String name, Object... params) {
        javax.persistence.Query query = getEntityManager().createNamedQuery(
                name);

        for (int i = 0; i < params.length; i++) {
            query.setParameter(i + 1, params[i]);
        }

        final List<T> result = (List<T>) query.getResultList();
        return result;
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<T> findByNamedQueryAndNamedParams(final String name,
                                                  final Map<String, ? extends Object> params) {
        javax.persistence.Query query = getEntityManager().createNamedQuery(
                name);

        for (final Map.Entry<String, ? extends Object> param : params
                .entrySet()) {
            query.setParameter(param.getKey(), param.getValue());
        }
        final List<T> result = (List<T>) query.getResultList();
        return result;
    }

    @Override
    public Class<T> getEntityClass() {
        return entityClass;
    }

/*
    @PersistenceContext
    public void setEntityManager(final EntityManager entityManager) {
        this.entityManager = entityManager;
    }
*/

    @Override
    public EntityManager getEntityManager() {
        return EntityManagerUtil.getEntityManager();
    }

    /**
     * Use this inside subclasses as a convenience method.
     */
    @Override
    public List<T> findByCriteria(final Criterion... criterion) {
        return findByCriteria(-1, -1, criterion);
    }

    /**
     * Use this inside subclasses as a convenience method.
     */
    @Override
    public List<T> findByCriteria(Order order, final Criterion... criterion) {
        return findByCriteria(-1, -1, order, criterion);
    }

    /**
     * Use this inside subclasses as a convenience method.
     */
    @Override
    @SuppressWarnings("unchecked")
    public List<T> findByCriteria(final int firstResult,
                                  final int maxResults, final Criterion... criterion) {
        Session session = (Session) getEntityManager().getDelegate();
        Criteria crit = session.createCriteria(getEntityClass());

        for (final Criterion c : criterion) {
            crit.add(c);
        }

        if (firstResult > 0) {
            crit.setFirstResult(firstResult);
        }

        if (maxResults > 0) {
            crit.setMaxResults(maxResults);
        }

        final List<T> result = crit.list();
        return result;
    }

    /**
     * Use this inside subclasses as a convenience method.
     */
    @Override
    @SuppressWarnings("unchecked")
    public List<T> findByCriteria(final int firstResult,
                                  final int maxResults, Order order, final Criterion... criterion) {
        Session session = (Session) getEntityManager().getDelegate();
        Criteria crit = session.createCriteria(getEntityClass());

        for (final Criterion c : criterion) {
            crit.add(c);
        }

        if (firstResult > 0) {
            crit.setFirstResult(firstResult);
        }

        if (maxResults > 0) {
            crit.setMaxResults(maxResults);
        }
        crit.addOrder(order);
        final List<T> result = crit.list();
        return result;
    }

    @Override
    public int countByCriteria(Criterion... criterion) {
        Session session = (Session) getEntityManager().getDelegate();
        Criteria crit = session.createCriteria(getEntityClass());
        crit.setProjection(Projections.rowCount());

        for (final Criterion c : criterion) {
            crit.add(c);
        }

        return ((Number) crit.list().get(0)).intValue();
    }


    @Override
    public void delete(final EntityManager em, final T entity) {
        em.remove(entity);
    }

    @Override
    public T save(final EntityManager em, final T entity) {
        final T savedEntity = em.merge(entity);
        return savedEntity;
    }

    @Override
    public void deleteCommit(final T entity) {
        EMF.runTransaction(getEntityManager(), new EMF.EMSession<java.lang.Object>() {
            @Override
            public Object transaction() {
                getEntityManager().remove(entity);
                return null;
            }
        });
    }


    @Override
    public T saveCommit(final T entity) {
        return EMF.runTransaction(getEntityManager(), new EMF.EMSession<T>() {
            @Override
            public T transaction() {
                final T savedEntity = getEntityManager().merge(entity);
                return savedEntity;
            }
        });
    }

}
